﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Timers;
using System.Windows.Threading;
using System.IO;
using System.Xml.Serialization;
using MinecraftProtocol.GameState;

namespace MinecraftMapViewer
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        TcpProxy.TcpProxy _proxy;
        MinecraftProtocol.MinecraftProtocolDecoder _serverDecoder, _clientDecoder;

        MinecraftProtocol.GameState.GameState _gameState;

        DispatcherTimer _timer;

        List<Block> _blocks;
        Dictionary<int, Mob> _mobs = new Dictionary<int, Mob>();

        Mob PlayerUi;

        bool _mapReady = false;


        #region Setup/Term
        public MainWindow()
        {
            InitializeComponent();

            loadBlockTypeSelection();

            PlayerUi = new Mob();
            PlayerUi.Width = Block.CxBlock;
            PlayerUi.Height = Block.CxBlock;
            MapCanvas.Children.Add(PlayerUi);

            _timer = new DispatcherTimer(new TimeSpan(0, 0, 0, 0, 50), DispatcherPriority.Normal, new EventHandler(timerCallback), this.Dispatcher);
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            saveBlockTypeSelection();


            stopProxy();
            killDecoderAndGameState();
        }
        #endregion

        #region Block Type Management
        private void loadBlockTypeSelection()
        {
            List<BlockType> blockTypes = new List<BlockType>();
            string selectionXml = Properties.Settings.Default.BlockTypeSelection;
            if ((selectionXml != null) && (selectionXml != ""))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(List<BlockType>));
                blockTypes = (List<BlockType>)serializer.Deserialize(new MemoryStream(System.Text.Encoding.UTF8.GetBytes(selectionXml)));
            }
            else
            {
                foreach (string blockTypeName in Enum.GetNames(typeof(MinecraftProtocol.Types.BlockTypes)))
                {
                    BlockType bt = new BlockType();
                    bt.Checked = false;
                    bt.Name = blockTypeName;
                    blockTypes.Add(bt);
                }
            }

            BlockTypeList.ItemsSource = blockTypes;
        }

        private void saveBlockTypeSelection()
        {
            XmlSerializer serializer = new XmlSerializer(typeof(List<BlockType>));
            MemoryStream ms = new MemoryStream();
            serializer.Serialize(ms, (List<BlockType>)BlockTypeList.ItemsSource);


            Properties.Settings.Default.BlockTypeSelection = System.Text.Encoding.UTF8.GetString(ms.ToArray());
            Properties.Settings.Default.Save();
        }
        #endregion

        #region Rendering
        private void UpdateMapButton_Click(object sender, RoutedEventArgs e)
        {
            prepareMap();
        }

        void timerCallback(object sender, EventArgs e)
        {
            //check game state
            //if (!_mapReady) return;

            if (_gameState == null) return;

            updateStats();

            updatePlayer();

            updateMobs();

            updateMap();
        }

        private void updateStats()
        {
            //update stats
            string stat = string.Format(
                "Position:\r\n" +
                "  X: {0:0.00}\r\n" +
                "  Y: {1:0.00}\r\n" +
                "  Z: {2:0.00}\r\n" +
                "View:\r\n" +
                "  Yaw: {3:0.00}\r\n" +
                "  Pitch: {4:0.00}\r\n" +
                "Entities: {5}\r\n" +
                "Server Broken: {6}\r\n" +
                "Client Broken: {7}\r\n",
                 _gameState.Player.Position.X, _gameState.Player.Position.Y, _gameState.Player.Position.Z,
                 _gameState.Player.View.Yaw, _gameState.Player.View.Pitch,
                 _gameState.Entities.Count(),
                _gameState.ServerDataBroken ? "Yes" : "No",
                _gameState.ClientDataBroken ? "Yes" : "No");

            StatusLabel.Content = stat;
        }

        private void updatePlayer()
        {
            PlayerUi.SetYaw(+_gameState.Player.View.Yaw - 180);

            //position the player
            PlayerUi.SetValue(Canvas.LeftProperty, (_gameState.Player.Position.X * Block.CxBlock) - (Block.CxBlock / 2));
            PlayerUi.SetValue(Canvas.TopProperty, (_gameState.Player.Position.Z * Block.CxBlock) - (Block.CxBlock / 2));
        }

        private void updateMobs()
        {
            lock (_gameState.Entities)
            {
                foreach (KeyValuePair<int, Entity> kvp in _gameState.Entities)
                {
                    Mob mob;
                    if (!_mobs.ContainsKey(kvp.Key))
                    {
                        mob = new Mob();
                        mob.Width = Block.CxBlock;
                        mob.Height = Block.CxBlock;
                        _mobs.Add(kvp.Key, mob);
                        MapCanvas.Children.Add(mob);
                        mob.SetName(kvp.Value.Name);
                    }
                    else
                    {
                        mob = _mobs[kvp.Key];
                    }


                    if ((Math.Abs(kvp.Value.Position.X - _gameState.Player.Position.X) < 5) &&
                      (Math.Abs(kvp.Value.Position.Z - _gameState.Player.Position.Z) < 5))
                    {
                    }
                    mob.SetValue(Canvas.LeftProperty, (kvp.Value.Position.X * Block.CxBlock) - (Block.CxBlock / 2));
                    mob.SetValue(Canvas.TopProperty, (kvp.Value.Position.Z * Block.CxBlock) - (Block.CxBlock / 2));
                }
            }

        }

        private void updateMap()
        {
            //position the mapcanvas so it's centered on the player
            MapCanvas.SetValue(Canvas.LeftProperty, -(_gameState.Player.Position.X * Block.CxBlock) + ((ViewportCanvas.ActualWidth - Block.CxBlock) / 2));
            MapCanvas.SetValue(Canvas.TopProperty, -(_gameState.Player.Position.Z * Block.CxBlock) + ((ViewportCanvas.ActualHeight - Block.CxBlock) / 2));
        }

        private void prepareMap()
        {
            _mapReady = false;

            if (_blocks == null) _blocks = new List<Block>();

            if (_mobs == null) _mobs = new Dictionary<int, Mob>();

            foreach (Block block in _blocks)
                MapCanvas.Children.Remove(block);
            _blocks.Clear();

            foreach (KeyValuePair<int, Mob> kvp in _mobs)
                MapCanvas.Children.Remove(kvp.Value);
            _mobs.Clear();

            Dictionary<Point, Point> matches = null;

            lock (_gameState.Map) //locked because otherwise more map could be added after map stats but before gettopmost
            {
                _gameState.UpdateMapStats();

                List<byte> blockTypesToMatch = new List<byte>();

                foreach (BlockType bt in (List<BlockType>)BlockTypeList.ItemsSource)
                {
                    if (bt.Checked)
                        blockTypesToMatch.Add((byte)(int)Enum.Parse(typeof(MinecraftProtocol.Types.BlockTypes), bt.Name));
                }

                matches = _gameState.GetTopmostBlocksByType(blockTypesToMatch.ToArray());
            }

            //add a block for each match, cap rendering
            int blocksAdded = 0;
            foreach (KeyValuePair<Point, Point> kvp in matches)
            {
                Block b = new Block();
                _blocks.Add(b);

                b.SetValue(Canvas.LeftProperty, kvp.Key.X * Block.CxBlock);
                b.SetValue(Canvas.TopProperty, kvp.Key.Y * Block.CxBlock);
                b.SetBlock((byte)kvp.Value.X, (int)kvp.Value.Y);

                b.Width = Block.CxBlock;
                b.Height = Block.CxBlock;

                MapCanvas.Children.Add(b);
                blocksAdded++;
                if (blocksAdded > 1000)
                    break;
            }

            //PlayerMob.SetValue(Canvas.ZIndexProperty, 1);

            _mapReady = true;
        }
        #endregion

        #region Proxy Management
        private void StartProxyButton_Click(object sender, RoutedEventArgs e)
        {
            startProxy();
        }

        private void StopProxyButton_Click(object sender, RoutedEventArgs e)
        {
            stopProxy();
            killDecoderAndGameState();
        }

        private void startProxy()
        {
            stopProxy();

            _proxy = new TcpProxy.TcpProxy(
                 Properties.Settings.Default.RemoteHost,
                 Properties.Settings.Default.RemotePort,
                 Properties.Settings.Default.LocalPort,
                 TcpProxy.BlockingStreamReader.Endianness.BigMotorola);

            _proxy.ProxySessionStarted += new EventHandler(_proxy_ProxySessionStarted);
            _proxy.ProxySessionFinished += new EventHandler(_proxy_ProxySessionFinished);
            _proxy.Start();
        }

        private void stopProxy()
        {
            if (_proxy != null)
            {
                _proxy.ProxySessionStarted -= new EventHandler(_proxy_ProxySessionStarted);
                _proxy.ProxySessionFinished -= new EventHandler(_proxy_ProxySessionFinished);
                _proxy.Stop();
                _proxy = null;
            }
        }

        void _proxy_ProxySessionStarted(object sender, EventArgs e)
        {
            killDecoderAndGameState();

            //File.Delete("d:\\temp\\server.bin");
            //File.Delete("d:\\temp\\client.bin");

            //_proxy.ServerPacketDumpFile = "d:\\temp\\server.bin";
            //_proxy.ClientPacketDumpFile = "d:\\temp\\client.bin";

            _serverDecoder = new MinecraftProtocol.MinecraftProtocolDecoder(true);
            _clientDecoder = new MinecraftProtocol.MinecraftProtocolDecoder(false);

            _gameState = new MinecraftProtocol.GameState.GameState();
            //_gameState.EntityChanged += new EventHandler(_gameState_EntityChanged);

            _serverDecoder.SessionState = _gameState;
            _clientDecoder.SessionState = _gameState;

            _proxy.SetProtocolDecoders(_serverDecoder, _clientDecoder);

            _serverDecoder.Start();
            _clientDecoder.Start();
        }

        void _proxy_ProxySessionFinished(object sender, EventArgs e)
        {
            killDecoderAndGameState();
        }

        void killDecoderAndGameState()
        {
            if (_serverDecoder != null)
            {
                _serverDecoder.Stop();
                _serverDecoder = null;
            }

            if (_clientDecoder != null)
            {
                _clientDecoder.Stop();
                _clientDecoder = null;
            }

            if (_gameState != null)
            {
                //_gameState.EntityChanged -= new EventHandler(_gameState_EntityChanged);
                _gameState = null;
            }
        }
        #endregion




    }
}
